/**?????????https://www.jianshu.com/p/4a16b315ae0a
 * LBS地址位置
 * lbs : { type: Array, index: '2d', sparse: true } //地理位置
 * 基于LBS的条件查询，Schema中定义时如上
 * LBS查询对于一些基于LBS应用会用得比较多。
 */

/**
 * 封装数据库的增删改查的SQL方法
 * NOTE: 错误优先回调
 * 字段名[field]可以是['parent.child']这样的字符串
 */
module.exports = {
  /**
   * CatModel.create({name: 'Dot'} , callback) => sava不会覆盖,只管把实体 entity往里存
   * model调用的是create方法，entity调用的是save方法
   * @param {*} CollName 
   * @param {Array | 多个集合} insertData
   * Person.create({name:'Ali'},{name:'PengHu'})
   * Person.create(array) 
   */
  tCreate(Collection, insertData) {
    return new Promise((resolve, reject) => {
      Collection.create(insertData).exec((err, docs) => {
        err ? reject(err) : resolve(docs)
      })
    })
  },
  /**
   * 不会覆盖,为了避免重复先查再存(entity的好处是字段和默认值齐全)
   * @param {*} Collection 
   * @param {*} insertData 
   */
  tSave(Collection, insertData) {
    return new Promise((resolve, reject) => {
      new Collection(insertData).save().exec((err, docs) => {
        err ? reject(err) : resolve(docs)
      })
    })
  },

  /****************************** 2删除 ******************************/
  /**
   * 
   * remove如果在document上调用，表示删除这条文档。z在model上调用，表示删除所有匹配的文档。
   * 直接向MongoDB发送一个remove命令，不涉及Mongoose文档。因为不涉及Mongoose文档，所以不执行中间件（hook）
   * 要删除文档而不等待MongoDB的响应，不要传递回调，然后在返回的Query上调用exec
   * @param {*} CollName 
   * @param {*} query 
   * @param {*} n 删除一个n为1
   */
  tRemove(CollName, query, n = null) {
    return new Promise((resolve, reject) => {
      CollName.remove(query, n).exec((err, data) => {
        err ? reject(err) : resolve(data)
      })
    })
  },
  /**
   * 
   * @param {Schema} CollName 
   * @param {String} field 
   * @param {Array} aDelete 例如: [ 'id1', 'id2', ... ]
   * 根据id获取删除嵌套数组中-所有在这个数组中的文档
   * https://blog.csdn.net/seaalan/article/details/89852767
   */
  tDelSelected(CollName, field, aDelete) {
    return new Promise((resolve, reject) => {
      CollName.deleteMany({ [field]: { $in: aDelete } }).exec((err, data) => {
        err ? reject(err) : resolve(data)
      })
    })
  },
  /**
   * 找到并删除
   */
  tFindAndRemove(Collection, query) {
    return new Promise((resolve, reject) => {
      Collection.findOneAndRemove(query).exec((err, docs) => {
        err ? reject(err) : resolve(docs)
      })
    })
  },



  /*********************************3 更新3个函数 ************************************/
  /**
   * 更新一条, 返回的是操作结果,而不是文档
   * @param {*} Collection 
   * @param {*} query 
   * @param {*} update 
   */
  tUpdateOne(Collection, query, update) {
    return new Promise((resolve, reject) => {
      Collection.updateOne(query, update).exec((err, docs) => {
        err ? reject(err) : resolve(docs)
      })
    })
  },
  /**
   * 更新多条, 返回的是操作结果,而不是文档
   * @param {*} Collection 
   * @param {*} query 
   * @param {*} update
   * 
    update: {
      $set: {tel: 000, ...},
      $inc: { age: 16 } 减则加上复数
    }
   */
  tUpdateMany(Collection, query, update) {
    return new Promise((resolve, reject) => {
      Collection.updateMany(query, update).exec((err, docs) => {
        err ? reject(err) : resolve(docs)
      })
    })
  },
  /**
   * update已经被废弃了,且不会把文档返回给应用层; 更新单独一条文档并且返回给应用层可使用find和update组合式
   * findByIdAndUpdate、findOneAndUpdate
   * $set $inc
    // $push是直接向数组中进行添加，如果不想添加重复的元素，可以选择使用$addToSet
    // $addToSet 可以给嵌套结构中文档的array中添加不重复的对象
    // $pull则是删除内嵌数组中的某一元素
   */
  tFindAndUpdate(Collection, query, update, options = {
    // 默认值配置 options控制选项
    new: true,// - 默认为false。 true为返回修改后的数据。
    upsert: true,// - 默认为false。 true为如果不存在则创建记录。
    setDefaultsOnInsert: {},// 如果upsert选项为true，在新建时插入文档定义的默认值。
    runValidators: false,// 如果值为true，执行Validation(权限)验证。
    sort: true,// 如果有多个查询条件，按顺序进行查询更新。
    select: { _id: 0, __v }, // 设置数据的返回(下划线id不用返回)
    // maxTimeMS // 对查询设置时间限制
  }) {
    return new Promise((resolve, reject) => {
      Collection.findOneAndUpdate(query, update, options).exec((err, docs) => {
        err ? reject(err) : resolve(docs)
      })
    })
  },

  /**************************4 查找-5个函数 ********************************/
  /**
   * 查一个
   * @param {*} Collection 
   * @param {*} query 
   * @param {*} showObj 
   */
  tFindOne(Collection, query, showObj = { _id: 0, __v: 0 }) {
    return new Promise((resolve, reject) => {
      Collection.findOne(query, showObj).exec((err, docs) => {
        err ? reject(err) : resolve(docs)
      })
    })
  },
  /**
   * 查多个
   * @param {*} Collection 
   * @param {*} query 
   * @param {*} showObj 
   * @param {*} sort 
   */
  tFind(Collection, query, showObj = { _id: 0, __v: 0 }, sort) {
    return new Promise((resolve, reject) => {
      Collection.find(query, showObj).sort(sort).exec((err, docs) => {
        err ? reject(err) : resolve(docs)
      })
    })
  },
  /**
   * 分页排序
   */
  tFindPage(Collection, query, pageSize, currentPage, sort = {}, showObj = { _id: 0, __v: 0 }) {
    /**
     * whereObj: {
     *  $or: [
     *    {name: 'xxx'},
     *    {password: 'xxx'},
     *    {username: /^xxx$/'}
     *    //正则模糊查找: /^xxx/ /^xxx$/ /xxx$/ new RegExp('/^xxx/')
     *  ]
     * }
     * 
     * sortObj: {
     *  year: -1// -1是降序
     * }
     * */
    let skipnum = (currentPage - 1) * pageSize;
    return new Promise((resolve, reject) => {
      Collection.find(query, showObj).skip(skipnum).limit(pageSize).sort(sort).exec((err, docs) => {
        err ? reject(err) : resolve(docs)
      })
    })
  },
  /**
   * 
   * @param {Schema} CollName 
   * @param {String} field 
   * @param {Array} aFind
   * 根据id获取它们的详细信息 例如: [ 'id1', 'id2', ... ]
   */
  tFindSelected(Collection, field, aFind, showObj = { _id: 0, __v: 0 }) {
    return new Promise((resolve, reject) => {
      Collection.find({
        // 加中括号表示key值也是变量
        [field]: { $in: aFind }
      }, showObj).exec((err, docs) => {
        err ? reject(err) : resolve(docs)
      })
    })
  },

  /**
   * User.where('age').gte(21).lte(65).exec(callback);
   * 未测试: 可能$in和.in的作用相同
    findSelected2(CollName, field, aFind) {
      return new Promise((resolve, reject) => {
        CollName.find({ vendorId: mongoose.Types.ObjectId(_vendorId), status: 'Active' })
          .where('category')
          .in(arr)
          .exec((err, data) => {
            err ? reject(err) : resolve(data)
          });
      })
    },
  */

  /* 1去重的字段 2筛选条件，为可选字段 3options 为其他选项一般应用较少
    distinct 有去重上限 Error: distinct too big, 16mb cap
    Person.distinct('name', { age: 22 },{
        collation: {
          locale: <string>,
          caseLevel: <boolean>,
          caseFirst: <string>,
          strength: <int>,
          numericOrdering: <boolean>,
          alternate: <string>,
          maxVariable: <string>, 
          backwards: <boolean>
        }}, function (err, result) {
      if (err) {
        return console.error(err);
      }
      console.log(result);//[ '李寻欢', '爱因斯坦', 'noshower' ]
    });
  */

  // 计算个数
  tCount(Collection, query = {}) {
    return new Promise((resolve, reject) => {
      Collection.count(query).exec((err, count) => {
        err ? reject(err) : resolve(count)
      });
    })
  }
}